package com.ah.ues;

import com.ah.ues.utils.GsonUtil;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class DingTalkApiClient {
    private static final Logger log = LoggerFactory.getLogger(DingTalkApiClient.class);
    private final String appKey;
    private final String appSecret;
    private String cachedAccessToken;
    private long tokenExpirationTime;
    private static final String BASE_URL = "https://oapi.dingtalk.com";
    private static final String BASE_URL_OAuth2 = "https://api.dingtalk.com";

    private final OkHttpClient httpClient = new OkHttpClient.Builder()
            .connectTimeout(Duration.ofSeconds(3))
            .writeTimeout(Duration.ofSeconds(3))
            .readTimeout(Duration.ofSeconds(3))
            .build();

    public DingTalkApiClient(String appKey, String appSecret) {
        this.appKey = appKey;
        this.appSecret = appSecret;
    }

    public String getAccessTokenWithRefresh() throws IOException {
        long currentTime = System.currentTimeMillis();
        if (cachedAccessToken == null || currentTime >= tokenExpirationTime) {
            // Token is expired or not yet fetched
            // https://open.dingtalk.com/document/orgapp/obtain-orgapp-token
            String url = BASE_URL + "/gettoken?appkey=" + appKey + "&appsecret=" + appSecret;
            String response = sendGetRequest(url);
            Map<String, Object> result = GsonUtil.toMap(response);
            if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
                log.error("获取token异常:{}", GsonUtil.obj2Json(response));
                throw new RuntimeException("获取钉钉token异常:" + result.get("errmsg"));
            }
            String accessToken = (String) result.get("access_token");
            int expires_in = (Integer) result.get("expires_in") + 1000; // 返回s
            if (StringUtils.isBlank(accessToken)) {
                throw new RuntimeException("获取钉钉token失败,请检查配置是否正确");
            }
            cachedAccessToken = accessToken;
            tokenExpirationTime = currentTime + expires_in;
        }
        return cachedAccessToken;
    }

    public String getOAuth2AccessTokenWithRefresh(String code) {
        // https://open.dingtalk.com/document/orgapp/obtain-user-token
        String url = BASE_URL_OAuth2 + "/v1.0/oauth2/userAccessToken";
        Map<String, Object> param = new HashMap<>();
        param.put("clientId", appKey);
        param.put("clientSecret", appSecret);
        param.put("code", code);
        param.put("grantType", "authorization_code");

        String response = null;
        try {
            response = sendPostRequest(url, GsonUtil.obj2Json(param));
        } catch (IOException e) {
            throw new RuntimeException("通过code获取userToken失败" + e.getMessage());
        }
        // {"expireIn":7200,"accessToken":"fdccdca833fd3af89c191f0bea6d85f8","refreshToken":"37973f815bc6355190f2c56ad6c460a0"}
        Map<String, Object> result = GsonUtil.toMap(response);
        return (String) result.get("accessToken");
    }

    public String getUniqIdByUserToken(String userToken) {
        // https://open.dingtalk.com/document/orgapp/dingtalk-retrieve-user-information
        String url = BASE_URL_OAuth2 + "/v1.0/contact/users/me";
        Request request = new Request.Builder()
                .url(url)
                .addHeader("x-acs-dingtalk-access-token", userToken)
                .get()
                .build();
        try (Response resp = httpClient.newCall(request).execute()) {
            if (!resp.isSuccessful()) {
                throw new RuntimeException("请求失败: " + resp.body().string());
            }
            // {"nick":"11","unionId":"11","avatarUrl":"","openId":"111","mobile":"11","stateCode":"86","visitor":false}
            return resp.body().string();
        } catch (IOException e) {
            throw new RuntimeException("通过userToken获取用户信息失败:" + e.getMessage());
        }
    }

    /**
     * 获取部门列表
     */
    public List<Map<String, Object>> getSubDepartmentList(Long deptId) throws IOException {
        String url = BASE_URL + "/topapi/v2/department/listsub?access_token=" + getAccessTokenWithRefresh();
        if (deptId != null) {
            url += "&dept_id=" + deptId;
        }
        String response = sendGetRequest(url);
        Map<String, Object> result = GsonUtil.toMap(response);
        return (List<Map<String, Object>>) result.get("result");
    }

    /**
     * 获取部门下的用户列表
     */
    public Map<String, Object> getUserList(Long deptId, int pageNo) throws IOException {
        // https://open.dingtalk.com/document/orgapp/queries-the-complete-information-of-a-department-user
        String url = BASE_URL + "/topapi/v2/user/list?access_token=" + getAccessTokenWithRefresh() + "&dept_id=" + deptId + "&cursor=" + pageNo + "&size=100";
        String response = sendGetRequest(url);
        Map<String, Object> result = GsonUtil.toMap(response);
        if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
            log.error("获取钉钉部门员工异常:{}", GsonUtil.obj2Json(response));
            throw new RuntimeException("获取钉钉部门员工失败:" + result.get("errmsg"));
        }
        return (Map<String, Object>) result.get("result");
    }

    /**
     * 根据userId获取用户详情
     * https://open.dingtalk.com/document/orgapp/query-user-details
     */
    public Map<String, Object> getByUserid(String userid) throws IOException {
        if (StringUtils.isBlank(userid)) {
            return null;
        }
        String url = BASE_URL + "/topapi/v2/user/get?access_token=" + getAccessTokenWithRefresh() + "&userid=" + userid;
        String response = sendGetRequest(url);
        Map<String, Object> result = GsonUtil.toMap(response);
        if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
            log.error("根据userId获取用户详情:{}", GsonUtil.obj2Json(response));
            throw new RuntimeException("获取钉钉用户详情失败:" + result.get("errmsg"));
        }
        return (Map<String, Object>) result.get("result");
    }

    /**
     * 根据unionid获取用户userid
     * https://open.dingtalk.com/document/orgapp/query-a-user-by-the-union-id
     *
     * @param unionid
     * @return
     * @throws IOException
     */
    public String getUseridByUniqId(String unionid) throws IOException {
        if (StringUtils.isBlank(unionid)) {
            return null;
        }
        String url = BASE_URL + "/topapi/user/getbyunionid?access_token=" + getAccessTokenWithRefresh() + "&unionid=" + unionid;
        String response = sendGetRequest(url);
        Map<String, Object> result = GsonUtil.toMap(response);
        if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
            log.error("根据unionid:{},获取用户userid失败:{}", unionid, GsonUtil.obj2Json(response));
            throw new RuntimeException("获取钉钉用户失败:" + result.get("errmsg"));
        }
        Map<String, Object> result1 = (Map<String, Object>) result.get("result");
        return (String) result1.get("userid");
    }

    // https://open.dingtalk.com/document/orgapp/query-department-details0-v2
    public Map<String, Object> getDeptDetail(Long deptId) throws IOException {
        if (deptId == null) {
            return null;
        }
        String url = BASE_URL + "/topapi/v2/department/get?access_token=" + getAccessTokenWithRefresh() + "&dept_id=" + deptId;
        String response = sendGetRequest(url);
        Map<String, Object> result = GsonUtil.toMap(response);
        if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
            log.error("获取部门详情失败:{}", GsonUtil.obj2Json(response));
            throw new RuntimeException("获取钉钉部门详情失败:" + result.get("errmsg"));
        }
        return (Map<String, Object>) result.get("result");
    }

    /**
     * https://open.dingtalk.com/document/orgapp/asynchronous-sending-of-enterprise-session-messages
     *
     * @param msg
     * @param agentId 发送消息时使用的微应用的AgentID
     * @param userIds aa,bb
     * @return
     * @throws IOException
     */

    public void sendMsgToUser(String msg, String agentId, String userIds) throws IOException {
        String url = BASE_URL + "/topapi/message/corpconversation/asyncsend_v2?access_token=" + getAccessTokenWithRefresh();
        Map<String, Object> param = new HashMap<>();
        param.put("agent_id", agentId);
        param.put("userid_list", userIds);
        param.put("msg", buildMsg(msg));

        String response = sendPostRequest(url, GsonUtil.obj2Json(param));
        Map<String, Object> result = GsonUtil.toMap(response);
        if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
            log.error("发送钉钉消息失败:{}", GsonUtil.obj2Json(response));
            throw new RuntimeException("发送钉钉消息失败:" + result.get("errmsg"));
        }
    }

    public void sendMsgMarkdownToUser(String msg, String agentId, String userIds) throws IOException {
        String url = BASE_URL + "/topapi/message/corpconversation/asyncsend_v2?access_token=" + getAccessTokenWithRefresh();
        Map<String, Object> param = new HashMap<>();
        param.put("agent_id", agentId);
        param.put("userid_list", userIds);
        param.put("msg", buildMarkdownMsg(msg));

        String response = sendPostRequest(url, GsonUtil.obj2Json(param));
        Map<String, Object> result = GsonUtil.toMap(response);
        if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
            log.error("发送钉钉消息失败:{}", GsonUtil.obj2Json(response));
            throw new RuntimeException("发送钉钉消息失败:" + result.get("errmsg"));
        }
    }

    private Map<String, Object> buildMsg(String msg) {
        Map<String, Object> map = new HashMap<>();
        map.put("msgtype", "text");
        Map<String, Object> textMap = new HashMap<>();
        textMap.put("content", msg);
        map.put("text", textMap);
        return map;
    }

    private Map<String, Object> buildMarkdownMsg(String msg) {
        Map<String, Object> map = new HashMap<>();
        map.put("msgtype", "markdown");
        Map<String, Object> textMap = new HashMap<>();
        textMap.put("title", "您有一条新的告警信息");
        textMap.put("text", msg);
        map.put("markdown", textMap);
        return map;
    }


    /**
     * 发送 GET 请求
     */
    private String sendGetRequest(String url) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("请求失败: " + response.body().string());
            }
            return response.body().string();
        }
    }

    private String sendPostRequest(String url, String json) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .post(RequestBody.create(json, MediaType.parse("application/json; charset=utf-8")))
                .build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("请求失败: " + response.body().string());
            }
            return response.body().string();
        }
    }

    public String getuserinfo_bycode(String code) throws Exception {
        // https://open.dingtalk.com/document/orgapp/obtain-the-user-information-based-on-the-sns-temporary-authorization
        long timestamp = System.currentTimeMillis();
        String sign = snsSign(this.appSecret, timestamp);
        String url = "https://oapi.dingtalk.com/sns/getuserinfo_bycode?accessKey=" + this.appKey + "&timestamp=" + timestamp + "&signature=" + sign;
        Map<String, Object> param = new HashMap<>();
        param.put("tmp_auth_code", code);

        String response = sendPostRequest(url, GsonUtil.obj2Json(param));
        Map<String, Object> result = GsonUtil.toMap(response);
        if (!Objects.equals(((Double)result.get("errcode")).intValue(), 0)) {
            log.error("getuserinfo_bycode失败:{}", GsonUtil.obj2Json(response));
            throw new RuntimeException("getuserinfo_bycode失败:" + result.get("errmsg"));
        }
        return response;
    }

    private String snsSign(String appSecret, long timestamp) throws Exception {
        // https://open.dingtalk.com/document/personalapp/signature-calculation-for-logon-free-scenarios-1?spm=ding_open_doc.document.0.0.1caf2ea7Rrk8On#title-vhq-oci-ujy
        // 根据timestamp, appSecret计算签名值
        String stringToSign = String.valueOf(timestamp);
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(appSecret.getBytes("UTF-8"), "HmacSHA256"));
        byte[] signatureBytes = mac.doFinal(stringToSign.getBytes("UTF-8"));
        String signature = new String(Base64.encodeBase64(signatureBytes));
        if ("".equals(signature)) {
            return "";
        }
        String encoded = URLEncoder.encode(signature, "UTF-8");
        String urlEncodeSignature = encoded.replace("+", "%20").replace("*", "%2A").replace("~", "%7E").replace("/", "%2F");
        return urlEncodeSignature;
    }
}
